#include "Level1.h"
#include "GLUtils.h"
#include "Game.h"
#include "GameOptions.h"
#include "Exception.h"
#include "Logger.h"
#include "Transformation.h"
#include "BoxCollider.h"
#include "Behaviour.h"
#include "CharacterController.h"
#include "CameraObject.h"
#include "TerrainObject.h"
#include "PlainTexture.h"
#include "Animation.h"
#include "ThirdPersonController.h"
#include "WanderInRegion.h"
#include "FancyText.h"
#include "SimpleText.h"
#include "HUDtimer.h"
#include "HUDlife.h"
#include "HUDplayerPosition.h"
#include "AudioSource.h"
#include "AudioControl.h"
#include "Size.h"
#include "Helpers.hpp"
#include "LevelCompleteScene.h"
#include "LevelFailedScene.h"
#include "Flocking.h"

using namespace game_engine;
using namespace game_engine::components;
using namespace game_engine::library;
using namespace game_engine::open_gl;
using namespace game_engine::utils;

///////////////////////////////////////////////////////////////////////////////
// Constructors and Destructors
///////////////////////////////////////////////////////////////////////////////

Level1::Level1(void)
    : Scene("level1"), sceneManager()
{
    sceneManager.setScene(this);
}

Level1::~Level1(void)
{
}

///////////////////////////////////////////////////////////////////////////////
// Other public methods
///////////////////////////////////////////////////////////////////////////////

const BOOL Level1::initialize(void)
{
    BOOL ok = true;
   
    try
    {
        // Create the objects of level1
        createObjects();
        // Initialize all game objects
        initGameObjects();
        // Initialize the scene manager
        sceneManager.initialize();

        // Graphics initialization
        GLUtils::commonLevelInit();
        GLUtils::levelInit(resolution.getWidth(), resolution.getHeight()); 

        // Play World music
        TerrainObject *world = dynamic_cast<TerrainObject*> (getObjectByName("World"));
        world->playBgm();

    } catch (exception &e) {
        Logger::FATAL(GAME_LOG, string("Error during level1 initialization : ") + 
            e.what());
        return false;
    }

    nextScene = this;

    return ok;
}

const BOOL Level1::update(const FLOAT32 time)
{
    // Update the whole scene
    BOOL ok = true;
    try
    {
        updateGameObjects(time);
        ok = sceneManager.update();
        if (!ok)
        {
            if (sceneManager.levelComplete())
            {
                LevelCompleteScene *levelComplete = new LevelCompleteScene();
                levelComplete->setResolution(Size(512, 640));
                nextScene = levelComplete;
            }
            else
            {
                LevelFailedScene *levelFailed = new LevelFailedScene();
                levelFailed->setResolution(Size(512, 640));
                nextScene = levelFailed;
            }
        }
    } catch (exception &e)
    {
        string err("Error during scene update: ");
        err.append(e.what());
        Logger::ERR(GAME_LOG, err);
        return false;
    }

  	if(keys[ESC_KEY])
    {
        // Quit game
        ok = false;
        keys[ESC_KEY] = false;
        nextScene = NULL;
    }

    return ok;
}

void Level1::finalize(void)
{
    try
    {
        // Finalize game objects
        finishGameObjects();
        // Finalize the scene manager
        sceneManager.finalize();
        // Stop World music
        TerrainObject *world = dynamic_cast<TerrainObject*> (getObjectByName("World"));
        world->stopBgm();
    } catch (exception &e)
    {
        string err("Error during scene finalization: ");
        err.append(e.what());
        Logger::ERR(GAME_LOG, err);
        throw RuntimeException(err);
    }
}

//void Level1::render(void)
//{
//}

///////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////

void Level1::createObjects(void)
{
    // TODO : order IS VERY IMPORTANT, so objects must have a priority in order
    // to make sure that methods like initialize, update, render ... are called
    // correctly

    // Create the terrain object
    createTerrain();

    // Create the player
    createPlayer();

    // Create the enemies
    createEnemies();

    // Create the powerups
    //createPowerups();
    
    // Create the HUD
    createHUD();

    // Create the obstacles
    createObstacles();

    // Create the cameras
    createCameras();

    // Advanced configurations
    CameraObject *mainCamera = dynamic_cast<CameraObject*> (getObjectByName("MainCamera"));
    //mainCamera->setFollowed(getObjectByName("Player"));
    HUDplayerPosition *playerPos = 
        dynamic_cast<HUDplayerPosition*> (getObjectByName("HUDPlayerPosIcon"));
    playerPos->setSubject(getObjectByName("Player"));

    // Configure EnemyB objects
    list<GameObject*>::const_iterator it;
    vector<GameObject*> others;
    vector<GameObject*> obstacles;
    for(it=objects.begin(); it!=objects.end(); it++)
    {
        if ((*it)->getTag() == "EnemyB")
        {
            Flocking *flock = dynamic_cast<Flocking*> ((*it)->getComponent("Flocking"));
            flock->setPlayer(getObjectByName("Player"));
            others.push_back((*it));
        }
        else if ((*it)->getTag() == "Obstacle")
        {
            obstacles.push_back((*it));
        }
    }

    for(it=objects.begin(); it!=objects.end(); it++)
    {
        if ((*it)->getTag() == "EnemyB")
        {
            Flocking *flock = dynamic_cast<Flocking*> ((*it)->getComponent("Flocking"));
            flock->setOthers(others);
            flock->setObstacles(obstacles);
        }
    }

    // Set the active camera
    activeCamera = mainCamera;
}

void Level1::createTerrain(void)
{
    Game *game = Game::getInstance();
    GameOptions options = game->gameOptions();

    TerrainObject *world = new TerrainObject(this, "World", "World", TERRAIN_LEVEL);
    world->setDescriptors("lvl2_idx.txt", "lvl1_tiles.png");
    world->setBgm("island.ogg", true);
    world->muteBgm(options.isMusicEnabled());

    // Add objects to current scene
    addObject(world);
}

void Level1::createPlayer(void)
{
    Game *game = Game::getInstance();
    GameOptions options = game->gameOptions();

    GameObject *player = new GameObject(this, "Player", "Player", ENTITY_LEVEL);

    // Create its components
    Transformation *playerTransf = new Transformation(player, 50.0, 70.0, 
        0.0, 0.0, 1.0, 1.0);
    Animation *playerAnim = new Animation(player, "animations.xml", "player.png");
    AudioSource *playerAudio = new AudioSource(player, "footsteps", EFFECT);
    playerAudio->setAudioFile("footsteps.ogg");
    playerAudio->setLoop(true);
    playerAudio->setMute(options.isFxEnabled());
    AudioControl *audioControl = new AudioControl(player);

    BoxCollider *playerCollider = new BoxCollider(player, Size(32.0f, 64.0f));
    ThirdPersonController *playerController = new ThirdPersonController(player);
    playerController->setIdleAnimationId("idle");
    playerController->setWalkAnimationId(EAST, "walkright");
    playerController->setWalkAnimationId(NORTH, "walkup");
    playerController->setWalkAnimationId(WEST, "walkleft");
    playerController->setWalkAnimationId(SOUTH, "walkdown");
    playerController->setWalkSpeed(PLAYER_WALK_SPEED);

    // Add components to object
    player->addComponent(playerTransf);
    player->addComponent(playerCollider);
    player->addComponent(playerController);
    player->addComponent(playerAnim);
    player->addComponent(playerAudio);
    player->addComponent(audioControl);

    // Add objects to current scene
    addObject(player);
}

void Level1::createEnemies(void)
{
    // Create enemies of type EnemyA, specifying:
    //  1. Initial position
    //  2. Region to wander in
    //  3. Walking speed
    //  4. Waiting time
    Game *game = Game::getInstance();
    GameOptions options = game->gameOptions();

    if (options.difficulty() == EASY)
    {
        createEnemyA(1, Point(250.0, 250.0), Rect(0.0f, 0.0f, 640.0f, 320.0f), ENEMY_SPEED_EASY, 4.0);
    }
    else if (options.difficulty() == MEDIUM)
    {
        //createEnemyA(1, Point(250.0, 250.0), Rect(0.0f, 0.0f, 640.0f, 320.0f), ENEMY_SPEED_MEDIUM, 4.0);
        //createEnemyA(2, Point(450.0, 250.0), Rect(120.0f, 0.0f, 400.0f, 400.0f), ENEMY_SPEED_MEDIUM, 4.0);
        createEnemyB(1, Point(25.0, 25.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(2, Point(50.0, 25.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(3, Point(75.0, 25.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(4, Point(100.0, 25.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(5, Point(25.0, 250.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(6, Point(50.0, 250.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(7, Point(75.0, 250.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(8, Point(100.0, 250.0), ENEMY_SPEED_MEDIUM);
        //createEnemyB(9, Point(125.0, 250.0), ENEMY_SPEED_MEDIUM);
        //createEnemyB(10, Point(150.0, 250.0), ENEMY_SPEED_MEDIUM);
        //createEnemyB(11, Point(175.0, 250.0), ENEMY_SPEED_MEDIUM);
        //createEnemyB(12, Point(200.0, 250.0), ENEMY_SPEED_MEDIUM);
    }
    else if (options.difficulty() == HARD)
    {
        //createEnemyA(1, Point(250.0, 250.0), Rect(0.0f, 0.0f, 640.0f, 320.0f), ENEMY_SPEED_HARD, 4.0);
        //createEnemyA(2, Point(450.0, 250.0), Rect(120.0f, 0.0f, 400.0f, 400.0f), ENEMY_SPEED_HARD, 4.0);
        //createEnemyA(3, Point(300.0, 25.0), Rect(0.0f, 0.0f, 640.0f, 500.0f), ENEMY_SPEED_HARD, 4.0);
        createEnemyB(1, Point(25.0, 25.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(2, Point(50.0, 25.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(3, Point(75.0, 25.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(4, Point(100.0, 25.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(5, Point(25.0, 250.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(6, Point(50.0, 250.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(7, Point(75.0, 250.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(8, Point(100.0, 250.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(9, Point(125.0, 250.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(10, Point(150.0, 250.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(11, Point(175.0, 250.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(12, Point(200.0, 250.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(13, Point(125.0, 25.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(14, Point(150.0, 25.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(15, Point(175.0, 25.0), ENEMY_SPEED_MEDIUM);
        createEnemyB(16, Point(200.0, 25.0), ENEMY_SPEED_MEDIUM);
    }
    else
    {
        throw IllegalArgumentException("unknown difficulty settings");
    }
}

void Level1::createPowerups(void)
{
    // Create powerups of type Star, specifying their position
    createStar(1, Point(50.0, 350.0));
    createStar(2, Point(350.0, 250.0));
    createStar(3, Point(450.0, 350.0));
}

void Level1::createObstacles(void)
{
    vector<Point> obstaclePositions;
    obstaclePositions.push_back(Point(175.0f, 190.0f));
    obstaclePositions.push_back(Point(375.0f, 420.0f));

    vector<Point>::iterator it;
    UINT32 i = 0;
    for (it=obstaclePositions.begin(); it<obstaclePositions.end(); it++)
    {
        // Define game object
        GameObject *obstacle = new GameObject(this, "Obstacle" + to_string<UINT32>(i, dec), 
            "Obstacle", ENTITY_LEVEL);

        // Create its components
        Transformation *transf = new Transformation(obstacle, (*it).getX(), (*it).getY(), 
            0.0, 0.0, 1.0, 1.0);
        PlainTexture *texture = new PlainTexture(obstacle, "plant.png", GL_RGBA, true);

        // Add components to object
        obstacle->addComponent(transf);
        obstacle->addComponent(texture);

        // Add object to scene
        addObject(obstacle);

        // Increment index
        i++;
    }
}

void Level1::createHUD(void)
{
    Game *game = Game::getInstance();
    GameOptions options = game->gameOptions();

    ///////////////////////////////////////////////////////////////////////////
    // Create a HUD timer
    ///////////////////////////////////////////////////////////////////////////
    GameObject *timerLabel = new GameObject(this, "TimerLabel", "Label", HUD_LEVEL);
    // Create its components
    Transformation *labelTransf = new Transformation(timerLabel, 220.0, 605.0, 
        0.0, 0.0, 1.0, 1.0);
    SimpleText *labelText = new SimpleText(timerLabel, TIMES_ROMAN_24, 
        ColorRGB(255.0, 255.0, 255.0));
    labelText->setText("TIME");
    // Add components to object
    timerLabel->addComponent(labelTransf);
    timerLabel->addComponent(labelText);
    
    HUDtimer *timer = new HUDtimer(this, "Time", "HUDtimer", HUD_LEVEL);
	timer->setMode(TIMER);
    // Set timer value according to game options
    if (options.difficulty() == EASY)
    {
        timer->setValue(TIMER_EASY);
    }
    else if (options.difficulty() == MEDIUM)
    {
        timer->setValue(TIMER_MEDIUM);
    }
    else if (options.difficulty() == HARD)
    {
        //timer->setValue(TIMER_HARD);
        timer->setValue(TIMER_MEDIUM);
    }
    else
    {
        throw IllegalArgumentException("unknown difficulty settings");
    }
	
	timer->setPosition(Point(240.0f, 580.0f));

	///////////////////////////////////////////////////////////////////////////
    // Create the SCORE label
    ///////////////////////////////////////////////////////////////////////////
    GameObject *scoreLabel = new GameObject(this, "ScoreLabel", "Label", HUD_LEVEL);

    // Create its components
    Transformation *slTransf = new Transformation(scoreLabel, 350.0, 605.0, 
        0.0, 0.0, 1.0, 1.0);
    SimpleText *slText = new SimpleText(scoreLabel, HELVETICA_12, 
        ColorRGB(255.0, 255.0, 255.0));
    slText->setText("SCORE : ");

    // Add components to object
    scoreLabel->addComponent(slTransf);
    scoreLabel->addComponent(slText);

	///////////////////////////////////////////////////////////////////////////
    // Create the SCORE value
    ///////////////////////////////////////////////////////////////////////////
    GameObject *scoreValue = new GameObject(this, "ScoreValue", "Label", HUD_LEVEL);

    // Create its components
    Transformation *svTransf = new Transformation(scoreValue, 430.0, 605.0, 
        0.0, 0.0, 1.0, 1.0);
    SimpleText *svText = new SimpleText(scoreValue, HELVETICA_12, 
        ColorRGB(255.0, 255.0, 255.0));
    svText->setText("0000");

    // Add components to object
    scoreValue->addComponent(svTransf);
    scoreValue->addComponent(svText);

	///////////////////////////////////////////////////////////////////////////
    // Create the LIFE label
    ///////////////////////////////////////////////////////////////////////////
    GameObject *lifeLabel = new GameObject(this, "LifeLabel", "Label", HUD_LEVEL);

    // Create its components
    Transformation *llTransf = new Transformation(lifeLabel, 350.0, 575.0, 
        0.0, 0.0, 1.0, 1.0);
    SimpleText *llText = new SimpleText(lifeLabel, HELVETICA_12, 
        ColorRGB(255.0, 255.0, 255.0));
    llText->setText("LIFES : ");

    // Add components to object
    lifeLabel->addComponent(llTransf);
    lifeLabel->addComponent(llText);

	///////////////////////////////////////////////////////////////////////////
    // Create the LIFE value
    ///////////////////////////////////////////////////////////////////////////
    HUDlife *lifeBar = new HUDlife(this, "LifeBar", "Label", HUD_LEVEL);
    // Set number of lifes according to game options
    if (options.difficulty() == EASY)
    {
        lifeBar->setMaxLifes(LIFES_EASY);
    }
    else if (options.difficulty() == MEDIUM)
    {
        lifeBar->setMaxLifes(LIFES_MEDIUM);
    }
    else if (options.difficulty() == HARD)
    {
        lifeBar->setMaxLifes(LIFES_HARD);
    }
    else
    {
        throw IllegalArgumentException("unknown difficulty settings");
    }

    lifeBar->setTextureFile("heart.png");
    lifeBar->setTextureType(GL_RGBA);
    lifeBar->setPosition(Point(430.0, 580.0));

	///////////////////////////////////////////////////////////////////////////
    // Create the PATH indicator
    ///////////////////////////////////////////////////////////////////////////
    // Path line object
    GameObject *pathLine = new GameObject(this, "PathLine", "", HUD_LEVEL);

    // Create its components
    Transformation *plTransf = new Transformation(pathLine, 17.0, 320.0, 
        0.0, 0.0, 1.0, 1.0);
    PlainTexture *plTexture = new PlainTexture(pathLine, "path.png", GL_RGBA, true);

    // Add components to object
    pathLine->addComponent(plTransf);
    pathLine->addComponent(plTexture);

    // Start label
    GameObject *startLabel = new GameObject(this, "SartLabel", "Label", HUD_LEVEL);

    // Create its components
    Transformation *stlTransf = new Transformation(startLabel, 4.0, 58.0, 
        0.0, 0.0, 1.0, 1.0);
    SimpleText *stlText = new SimpleText(startLabel, HELVETICA_12, 
        ColorRGB(255.0, 255.0, 255.0));
    stlText->setText("start");

    // Add components to object
    startLabel->addComponent(stlTransf);
    startLabel->addComponent(stlText);

    // Goal label
    GameObject *goalLabel = new GameObject(this, "GoalLabel", "Label", HUD_LEVEL);

    // Create its components
    Transformation *glTransf = new Transformation(goalLabel, 5.0, 580.0, 
        0.0, 0.0, 1.0, 1.0);
    SimpleText *glText = new SimpleText(goalLabel, HELVETICA_12, 
        ColorRGB(255.0, 255.0, 255.0));
    glText->setText("goal");

    // Add components to object
    goalLabel->addComponent(glTransf);
    goalLabel->addComponent(glText);

    // Face icon
    HUDplayerPosition *faceIcon = new HUDplayerPosition(this, "HUDPlayerPosIcon", "", HUD_LEVEL);
    faceIcon->setXPosition(16.0);
    faceIcon->setYminPosition(74.0);
    faceIcon->setYmaxPosition(576.0);
    faceIcon->setTextureFile("face.png");
    faceIcon->setTextureType(GL_RGBA);

    // Add objects to current scene
    addObject(timerLabel);
	addObject(timer);
    addObject(scoreLabel);
    addObject(scoreValue);
    addObject(lifeLabel);
    addObject(lifeBar);
    addObject(pathLine);
    addObject(startLabel);
    addObject(goalLabel);
    addObject(faceIcon);
}

void Level1::createCameras(void)
{
    CameraObject *mainCamera = new CameraObject(this, "MainCamera", "Camera", 
        HUD_LEVEL);
    mainCamera->setInitialPosition(Point(256.0, 320.0));
    mainCamera->setView(Rect(0.0, 0.0, resolution.getHeight(), 
        resolution.getWidth()));

    // Add object to current scene
    addObject(mainCamera);
}

void Level1::createEnemyA(const UINT32 index, const Point &position, 
                          const Rect &region, const FLOAT32 speed, 
                          const FLOAT32 waitTime)
{
    Game *game = Game::getInstance();
    GameOptions options = game->gameOptions();

    GameObject *enemyA = new GameObject(this, "Enemy" + to_string<UINT32>(index, dec), 
        "EnemyA", ENTITY_LEVEL);

    // Create its components
    Transformation *enemyATransf = new Transformation(enemyA, position.getX(), 
        position.getY(), 0.0, 0.0, 1.0, 1.0);
    Animation *enemyAAnim = new Animation(enemyA, "animations.xml", "enemyA.png");
    AudioSource *enemyAaudio = new AudioSource(enemyA, "bird", EFFECT);
    enemyAaudio->setAudioFile("bird.ogg");
    enemyAaudio->setMute(options.isFxEnabled());
    AudioControl *audioControl = new AudioControl(enemyA);
    BoxCollider *enemyACollider = new BoxCollider(enemyA, Size(32.0f, 32.0f));
    WanderInRegion *enemyAController = new WanderInRegion(enemyA, region);
    enemyAController->setIdleAnimationId("idle");
    enemyAController->setWalkAnimationId(EAST, "walkright");
    enemyAController->setWalkAnimationId(NORTH, "walkup");
    enemyAController->setWalkAnimationId(WEST, "walkleft");
    enemyAController->setWalkAnimationId(SOUTH, "walkdown");
    enemyAController->setWalkSpeed(speed);
    enemyAController->setMaxWaitingTime(waitTime);
    
    // Add components to object
    enemyA->addComponent(enemyATransf);
    enemyA->addComponent(enemyACollider);
    enemyA->addComponent(enemyAController);
    enemyA->addComponent(enemyAAnim);
    enemyA->addComponent(enemyAaudio);
    enemyA->addComponent(audioControl);

    // Add objects to current scene
    addObject(enemyA);
}

void Level1::createStar(const UINT32 index, const Point &position)
{
    GameObject *star = new GameObject(this, "Star" + to_string<UINT32>(index, dec), 
        "Star", ENTITY_LEVEL);

    // Create its components
    Transformation *starTransf = new Transformation(star, position.getX(), 
        position.getY(), 0.0, 0.0, 1.0, 1.0);
    Animation *starAnim = new Animation(star, "animations.xml", "kill_powerup.png");
    starAnim->resetSequence();
    starAnim->setActiveSequence("idle");
    BoxCollider *starCollider = new BoxCollider(star, Size(32.0f, 40.0f));

    // Add components to object
    star->addComponent(starTransf);
    star->addComponent(starAnim);
    star->addComponent(starCollider);

    // Add objects to current scene
    addObject(star);
}

void Level1::createEnemyB(const UINT32 index, const Point &position, const FLOAT32 speed)
{
    Game *game = Game::getInstance();
    GameOptions options = game->gameOptions();

    GameObject *enemyB = new GameObject(this, "Enemy" + to_string<UINT32>(index, dec), 
        "EnemyB", ENTITY_LEVEL);

    // Create its components
    Transformation *enemyBTransf = new Transformation(enemyB, position.getX(), 
        position.getY(), 0.0, 0.0, 1.0, 1.0);
    Animation *enemyBAnim = new Animation(enemyB, "animations.xml", "enemyA.png");
    //BoxCollider *enemyBCollider = new BoxCollider(enemyB, Size(32.0f, 32.0f));
    Flocking *enemyBController = new Flocking(enemyB);
    enemyBController->setAnimationId(IDLE, "idle");
    enemyBController->setAnimationId(EAST, "walkright");
    enemyBController->setAnimationId(NORTH, "walkup");
    enemyBController->setAnimationId(WEST, "walkleft");
    enemyBController->setAnimationId(SOUTH, "walkdown");
    //enemyBController->setSpeed(speed);
    //enemyBController->setGoalPosition(Point(25.0f, 25.0f));
    
    // Add components to object
    enemyB->addComponent(enemyBTransf);
    //enemyA->addComponent(enemyBCollider);
    enemyB->addComponent(enemyBController);
    enemyB->addComponent(enemyBAnim);

    // Add objects to current scene
    addObject(enemyB);
}
